package com.eraasoftware.npa.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.eraasoftware.npa.io.ChannelBuffer;
import com.eraasoftware.npa.io.Serializer;

public class RemoteService {
	private static final int MAX_CONNECT_ATTEMPT = 2;
	static int BUFFER_SIZE = 5000;
	boolean master = true;

	String host = "localhost";
	int port = 1234;
	// SocketChannel socketChannel;
	ChannelBuffer channelBuffer;
	Charset charset = Charset.forName("ISO-8859-1");

	CharBuffer cbuf = CharBuffer.allocate(BUFFER_SIZE);
	CharsetDecoder decoder = charset.newDecoder();
	StringBuilder builder = new StringBuilder();
	ProcessManager processManager = null;
	private int connectAttempt = 0;
	static Log log = LogFactory.getLog(RemoteService.class);

	public static void main(String[] args) {

		RemoteService remoteService = new RemoteService();
		String host = getArg("-H", "--host", args);
		if (host != null)
			remoteService.host = host;

		String portValue = getArg("-P", "--port", args);
		if (portValue != null) {
			remoteService.port = Integer.parseInt(portValue);
		}

		String slave = getArg("-s", "--slave", args);
		if (slave != null)
			remoteService.master = false;

		remoteService.start();

	}

	public RemoteService master(boolean master) {
		this.master = master;
		return this;
	}

	public boolean master() {
		return master;
	}

	public RemoteService remoteAddress(String host, int port) {
		this.host = host;
		this.port = port;
		return this;
	}

	public void start() {
		log.info("host " + host + ":" + port + " master: " + master);
		try {
			// if error is throw, only master stay alive
			while (master || connectAttempt++ < MAX_CONNECT_ATTEMPT) {
				if (connectToServer()) {
					listen();
				}

			}

		} catch (IOException e) {
			log.error(e);
		}
	}

	/**
	 * 
	 * @param alias
	 *            -P
	 * @param name
	 *            --port=
	 * @param args
	 *            program arguments
	 * @return
	 */
	static String getArg(String alias, String name, String[] args) {
		String res = null;
		for (int n = 0; n < args.length; n++) {
			String arg = args[n];
			if (arg.equals(alias)) {
				res = args[++n];
				break;
			}
			if (arg.startsWith(name)) {
				res = arg.substring(name.length() + 1);
				break;
			}
		}
		return res;
	}

	static String displayArguments() {
		StringBuilder b = new StringBuilder();
		b.append(" Usage : java -jar nap.jar [OPTIONS]\n");
		b.append("  -P, --port=#			Port number to use for the connection\n");
		b.append("  -H, --host=name	Host to connect\n");
		b.append(" -s, --slave=y|n 		Slave process, if server shut down, this process shutdown  too\n");
		return b.toString();
	}

	public boolean connectToServer() throws IOException {
		InetSocketAddress address = new InetSocketAddress(host, port);
		log.debug("try to connect -> :" + address);
		try {
			SocketChannel socketChannel = SocketChannel.open(address);
			channelBuffer = new ChannelBuffer(socketChannel, BUFFER_SIZE);
			return true;
		} catch (Exception e) {

			try {
				Thread.sleep(1000);

				// connectToServer();
			} catch (InterruptedException e1) {
				log.error("Fail to connect " + e.getMessage());
			}
			return false;
		}

	}

	protected void listen() throws IOException {

		Serializer headerParser = Serializer.instance();
		int read = 1;
		while (read > 0) {

			if ((read = channelBuffer.read()) > 0) {
				ByteBuffer buff = channelBuffer.buffer();
				Header head = headerParser.readObject(buff, Header.class);
				if (!channelBuffer.remaining()) {
					channelBuffer.clear();
				}
				log.info(head.action.toString());

				switch (head.action) {

				case NATIVE:
					handleActionNative(channelBuffer, head);
					break;

				case PING:
					handleActionPing(channelBuffer, head);
					break;

				case NEW_CLIENT:
					handleActionNewClient(channelBuffer, head);
					break;

				default:
					readBytesFromChannel(channelBuffer, head);
					break;

				}

				channelBuffer.send();

			}
		}

	}

	private void handleActionNewClient(ChannelBuffer channelBuffer2, Header head) throws IOException {

		try {
			getProcessManager().spawn();
			channelBuffer2.write("OK".getBytes());

		} catch (IOException e) {
			channelBuffer2.write("KO".getBytes());
			channelBuffer2.write(e.getMessage().getBytes());
		}

	}

	public ProcessManager getProcessManager() {
		if (processManager == null) {
			processManager = new ProcessManager(this);
			processManager.loggable(true);
		}
		return processManager;
	}

	@SuppressWarnings("unchecked")
	private void handleActionNative(ChannelBuffer buffer, Header head) throws IOException {

		if (head.getActionHandler() == null) {
			log.error("actionHandler is null");

		} else {
			try {
				Class<? extends ActionHandler> handlerClass = (Class<? extends ActionHandler>) Class.forName(head
						.getActionHandler());
				handlerClass.newInstance().received(head, buffer);
			} catch (ClassNotFoundException e) {
				log.error("error", e);
			} catch (InstantiationException e) {
				log.error("error", e);
			} catch (IllegalAccessException e) {
				log.error("error", e);
			}
		}

	}

	private void handleActionPing(ChannelBuffer channelBuffer2, Header head) throws IOException {

	}

	ByteArrayOutputStream baos = new ByteArrayOutputStream();

	protected ByteArrayOutputStream readBytesFromChannel(ChannelBuffer channelBuffer2, Header head) throws IOException {
		baos.reset();
		WritableByteChannel outChannel = Channels.newChannel(baos);

		while (channelBuffer2.read() > 0) {

			outChannel.write(channelBuffer.buffer());
		}
		return baos;

	}

	protected String getTxtRequestContentFromChannel(ReadableByteChannel channel, ByteBuffer buff) throws IOException {
		cbuf.position(buff.position());
		cbuf.limit(buff.limit());
		builder.setLength(0);
		decoder.decode(buff, cbuf, true);
		cbuf.flip();
		builder.append(cbuf.array());
		buff.clear();
		cbuf.clear();
		do {
			buff.flip();
			cbuf.flip();
			decoder.decode(buff, cbuf, true);
			builder.append(cbuf.array());
			buff.clear();
			cbuf.clear();
		} while (channel.read(buff) > 0);
		return builder.toString();

	}

	@Override
	protected void finalize() throws Throwable {
		try {
			if (processManager != null) {
				log.info("Kill all children");
				processManager.killAll();
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		super.finalize();
	}

}
